﻿#region Licence
// Copyright (c) 2011 BAX Services Bartosz Adamczewski
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CSpec.Testing;
using System.Reflection;
using CSpec.Shell.Display;
using Shell.Resources;
using System.Collections;

namespace CSpec.Shell.Execution
{
    /// <summary>
    /// Action that runs operations conatined in an asembly
    /// Additional Attributes (Commands):
    ///  -a AllCommand - displays detailed information about operation curently performed.
    ///  -f FileCommand - writes the console output to a file.
    /// </summary>
    public class TestRunnerAction : BaseAction, IAction
    {
        private ITestRunner runner = null;

        /// <summary>
        /// Default constructor that initializes the console and it's formatters (through base class), as
        /// well as a list of commands used.
        /// </summary>
        /// <remarks>
        /// Why are we calling base() explicitly ? 
        /// This is to note that we are using Console and the Runner from the
        /// BaseAction class that takes care of the console and formatting.
        /// </remarks>
        public TestRunnerAction() : base()
        {
            runner = new CSpecTestRunner();
            Commands = new List<ICommand>() { new Commands.RunnerAllCommand(1), new Commands.FileCommand(100) };
        }

        /// <summary>
        /// Gets List of commands used by this action
        /// </summary>
        public List<ICommand> Commands { get; private set; }

        /// <summary>
        /// Executes the action with specified console parameters
        /// </summary>
        /// <param name="args">application args</param>
        public void Execute(string[] args)
        {
            //In order to execute some of the commands in the right order for this action
            //we need to screate a sroted list by key (priority) and then loop through
            //that list and execute each command.
            SortedList<int, ICommand> commandsToExecute = new SortedList<int, ICommand>();
            SortedList<int, List<object>> commandsArgs = new SortedList<int, List<object>>();


            if (args[1] == null || args[1] == string.Empty)
            {
                Console.WriteError("No arguments");
                DisplayInterface();
            }
            else
            {
                int lastKey = -1;

                //skip the first two params, and go to the external attributes
                for (int i = 2; i < args.Length; i++)
                {
                    var command = Commands.Where(x => x.Key.ToUpper() == args[i].ToUpper()).FirstOrDefault();

                    //not a command, this means that this is a command argument.
                    if (command == null)
                    {
                        commandsArgs[lastKey].Add(args[i]);
                    }
                    else
                    {
                        commandsToExecute.Add(command.Priority, command);
                        commandsArgs.Add(command.Priority, new List<object>() {runner});
                        lastKey = command.Priority;
                    }
                }

                //Run commands that need to get invoked before the main operation.
                RunCommandsByPriority(0, 49, commandsToExecute, commandsArgs);

                //Load the assembly and start testing baby!
                Assembly asm = Assembly.LoadFrom(args[1]);
                runner.RunTestOnAssembly(asm);

                //Run commands that will get invoked after the main operation.
                RunCommandsByPriority(50, 101, commandsToExecute, commandsArgs);

                Console.WriteInfo("\n Tests passed: " + runner.Passed + "/" + (runner.Failed + runner.Passed));
            }
        }


        /// <summary>
        /// Runs the specified commands, based on a priority window.
        /// </summary>
        private void RunCommandsByPriority(int low, int high, SortedList<int, ICommand> commandsToExecute, SortedList<int, List<object>> commandsArgs)
        {
            foreach (var command in commandsToExecute.Values)
            {
                if (command != null && command.Priority > low && command.Priority < high)
                {
                    command.Execute(Console, commandsArgs[command.Priority].ToArray());
                }
            }
        }

        /// <summary>
        /// Displays the help interface to the console.
        /// </summary>
        public void DisplayInterface()
        {
            base.DisplayHelp(this.Key, this.Description, Commands, true);
        }

        /// <summary>
        /// Gets the key for this action. 
        /// </summary>
        public string Key
        {
            get { return "runner"; }
        }

        /// <summary>
        /// Gets the description for this action.
        /// </summary>
        public string Description
        {
            get { return ActionMessages.TestRunner; }
        }
    }
}
